/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.nodes;
import java.awt.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.util.Vector;
import java.util.ResourceBundle;
import java.io.IOException;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import org.openide.TopManager;
import org.openide.util.NbBundle;
import org.openide.util.WeakListener;
import org.openide.util.datatransfer.ExTransferable;
import org.openide.explorer.view.NodeRenderer;
/** A dialog for reordering nodes. This dialog can reorder
* nodes for all implementors of the {@link Index} cookie.
* The dialog can invoke reorder actions on a given <code>Index</code>
* implementation immediatelly, or these actions can be accumulated
* and invoked at once, when the dialog is closed.
*
* <p>This class is final only for performance reasons.
*
* @author Jan Jancura, Ian Formanek, Dafe Simonek
*/
public final class IndexedCustomizer extends JDialog
implements java.beans.Customizer {
// variables .....................................................................................
/** The actual JList control */
private JList control;
/** Buttons */
private JButton buttonUp, buttonDown, buttonClose;
/** index to sort */
private Index index;
private Node[] nodes;
/** Whether or not change the order immediatelly */
private boolean immediateReorder = true;
/** Permutation array, which stores moves in case when
* immediateReorder property is false */
private int[] permutation;
/** Listener to the changes in the nodes */
private ChangeListener nodeChangesL;
/** drag and drop support */
private IndexedDragSource dragSupport;
private IndexedDropTarget dropSupport;
// initializations ................................................................................
static final long serialVersionUID =-8731362267771694641L;
/** Construct a new customizer. */
public IndexedCustomizer () {
super (TopManager.getDefault().getWindowManager().getMainWindow (), true);
setDefaultCloseOperation (javax.swing.JDialog.DISPOSE_ON_CLOSE);
// attach cancel also to Escape key
getRootPane().registerKeyboardAction(
new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
setVisible (false);
dispose ();
}
},
javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0, true),
javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW
);
setTitle(Node.getString("LAB_order"));
JComponent p = (JComponent)getContentPane ();
p.setLayout (new java.awt.BorderLayout ());
p.setBorder (new EmptyBorder (8, 8, 8, 5));
JLabel l = new JLabel (Node.getString("LAB_listOrder"));
p.add (l, "North"); // NOI18N
control = new AutoscrollJList();
control.addListSelectionListener (new ListSelectionListener () {
public void valueChanged(ListSelectionEvent e) {
if (control.isSelectionEmpty ()) {
buttonUp.setEnabled (false);
buttonDown.setEnabled (false);
} else {
int i = control.getSelectedIndex ();
if (i > 0) //PENDING - jeste testovat, jestli jsou OrderedCookie.Child
buttonUp.setEnabled (true);
else
buttonUp.setEnabled (false);
if (i < (nodes.length - 1))
buttonDown.setEnabled (true);
else
buttonDown.setEnabled (false);
}
}
}
);
control.setCellRenderer (new IndexedListCellRenderer ());
control.setVisibleRowCount (15);
control.setSelectionMode (javax.swing.ListSelectionModel.SINGLE_SELECTION);
// list has to be scrolling
p.add (new JScrollPane(control), "Center"); // NOI18N
JPanel bb = new JPanel ();
buttonClose = new JButton (Node.getString("Button_close"));
buttonUp = new JButton (Node.getString("Button_up"));
buttonDown = new JButton (Node.getString("Button_down"));
bb.setLayout (new BorderLayout ());
bb.setBorder (new EmptyBorder (6, 7, 6, 7));
JPanel x = new JPanel ();
x.setLayout (new GridLayout (2, 1));
x.add (buttonUp);
x.add (buttonDown);
bb.add ("North", x); // NOI18N
bb.add ("South", buttonClose); // NOI18N
buttonUp.addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent e) {
int i = control.getSelectedIndex ();
moveUp (i);
updateList ();
control.setSelectedIndex (i - 1);
control.repaint ();
}
});
buttonDown.addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent e) {
int i = control.getSelectedIndex ();
moveDown (i);
updateList ();
control.setSelectedIndex (i + 1);
control.repaint ();
}
});
buttonClose.addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent e) {
if ((!immediateReorder) && (index != null) &&
(permutation != null)) {
int[] realPerm = new int[permutation.length];
for (int i = 0; i < realPerm.length; i++) {
realPerm[permutation[i]] = i;
//System.out.println (i + "-->" + permutation[i]); // NOI18N
}
index.reorder(realPerm);
}
dispose ();
}
});
buttonUp.setEnabled (false);
buttonDown.setEnabled (false);
p.add (bb, "East"); // NOI18N
// disable drag support, as DnD crashes all environment
// under current implementation of VMs on unixes, ans causes
// some deadlocks on windows...
//dragSupport = new IndexedDragSource(control);
//dropSupport = new IndexedDropTarget(this, dragSupport);
pack();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension dialogSize = getSize();
setLocation((screenSize.width-dialogSize.width)/2,(screenSize.height-dialogSize.height)/2);
buttonClose.requestFocus (); // to get shortcuts to work
}
// other methods ................................................................................
/** Called when an explored context changes and the list needs to be
* recreated.
*/
private void updateList () {
if (index == null)
return;
Node[] localNodes = index.getNodes();
//System.out.println ("Nodes taken, size: " + localNodes.length); // NOI18N
// obtain nodes with help from permutation array, if
// conditions met
if (!immediateReorder) {
getPermutation();
int origLength = permutation.length;
int newLength = localNodes.length;
if (origLength < newLength) {
// some nodes added, we must synchronize the permutation
nodes = new Node[newLength];
int[] newPerm = new int[newLength];
System.arraycopy(newPerm, 0, permutation, 0, origLength);
for (int i = 0; i < newLength; i++) {
if (i < origLength)
nodes[i] = localNodes[permutation[i]];
else {
// added nodes....
nodes[i] = localNodes[i];
newPerm[i] = i;
}
}
permutation = newPerm;
} else if (origLength > newLength) {
// some nodes removed, we must re-initialize the permutation
nodes = new Node[newLength];
permutation = new int[newLength];
for (int i = 0; i < newLength; i++) {
nodes[i] = localNodes[i];
permutation[i] = i;
}
} else {
// node count is the same, only permute the nodes
nodes = new Node[newLength];
for (int i = 0; i < newLength; i++)
nodes[i] = localNodes[permutation[i]];
}
} else {
nodes = (Node[])localNodes.clone();
}
control.setListData (nodes);
}
public Dimension getPreferredSize () {
return new Dimension (300, super.getPreferredSize ().height);
}
/** Will reorders be reflected immediately?
* @return <code>true</code> if so
*/
public boolean isImmediateReorder () {
return immediateReorder;
}
/** Set whether reorders will take effect immediately.
* @param immediateReorder <code>true</code> if so
*/
public void setImmediateReorder (boolean immediateReorder) {
if (this.immediateReorder == immediateReorder) return;
this.immediateReorder = immediateReorder;
if (immediateReorder) {
if (permutation != null) {
index.reorder(permutation);
permutation = null;
updateList();
}
}
}
// implementation of Customizer ............................................................
/** Set the nodes to reorder.
* @param bean must implement {@link Index}
* @throws IllegalArgumentException if not
*/
public void setObject (Object bean) {
if (bean instanceof Index) {
index = (Index)bean;
// add weak listener to the Index
nodeChangesL = new ChangeListener() {
public void stateChanged (ChangeEvent ev) {
SwingUtilities.invokeLater(new Runnable() {
public void run () {
updateList();
}
});
}
};
updateList ();
control.invalidate();
validate();
index.addChangeListener(WeakListener.change(nodeChangesL, index));
} else {
throw new IllegalArgumentException ();
}
}
// I don't change any property...
public void addPropertyChangeListener(PropertyChangeListener listener) {
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
}
/** Moves up. Performs differently according to
* immediateReorder property value.
*/
private void moveUp (final int position) {
if (index == null) return;
if (immediateReorder) {
index.moveUp(position);
} else {
getPermutation();
int temp = permutation[position];
permutation[position] = permutation[position - 1];
permutation[position - 1] = temp;
}
}
/** Moves down. Performs differently according to
* immediateReorder property value.
*/
private void moveDown (final int position) {
if (index == null) return;
if (immediateReorder) {
index.moveDown(position);
} else {
getPermutation();
int temp = permutation[position];
permutation[position] = permutation[position + 1];
permutation[position + 1] = temp;
}
}
/** Safe getter for permutation.
* Initializes permutation to identical permutation if it is null.<br>
* index variable must not be null when this method called */
private int[] getPermutation () {
if (permutation == null) {
if (nodes == null)
nodes = (Node[])index.getNodes().clone();
permutation = new int[nodes.length];
for (int i = 0; i < nodes.length; permutation[i] = i++);
}
return permutation;
}
/** Permute the list as given permutation dictates.
* Sets selection to the given selected index.
* Called from dropSupport as result of succesfull DnD operation.
*/
void performReorder (int[] perm, int selected) {
if (immediateReorder) {
index.reorder(perm);
} else {
// merge current and reversed given permutation
// (reverse given permutation first)
int[] reversed = new int[perm.length];
for (int i = 0; i < reversed.length; i++)
reversed[perm[i]] = i;
int[] orig = getPermutation();
permutation = new int[orig.length];
for (int i = 0; i < orig.length; i++) {
permutation[i] = orig[reversed[i]];
// System.out.println(permutation[i] + " ----> " + i); // NOI18N
}
}
updateList();
control.setSelectedIndex(selected);
control.repaint();
}
/** Implementation of drag functionality in
* reorder dialog. */
private static final class IndexedDragSource
implements DragGestureListener,
DragSourceListener {
/** Asociated JList component where the drag will
* take place */
JList comp;
/** User gesture that initiated the drag */
DragGestureEvent dge;
/** Out data flavor used to transfer the index */
DataFlavor myFlavor;
/** Creates drag source with asociated list where drag
* will take place.
* Also creates the default gesture and asociates this with
* given component */
IndexedDragSource (JList comp) {
this.comp = comp;
// initialize gesture
DragSource ds = DragSource.getDefaultDragSource();
ds.createDefaultDragGestureRecognizer(
comp, DnDConstants.ACTION_MOVE, this);
}
/** Initiating the drag */
public void dragGestureRecognized(DragGestureEvent dge) {
// check allowed actions
if ((dge.getDragAction() & DnDConstants.ACTION_MOVE) == 0)
return;
// prepare transferable and start the drag
int index = comp.locationToIndex(dge.getDragOrigin());
// no index, then no dragging...
if (index < 0)
return;
// System.out.println("Starting drag..."); // NOI18N
// create our flavor for transferring the index
myFlavor =
new DataFlavor(String.class,
NbBundle.getBundle(IndexedCustomizer.class).
getString("IndexedFlavor"));
try {
dge.startDrag(DragSource.DefaultMoveDrop,
new IndexTransferable(myFlavor, index), this);
// remember the gesture
this.dge = dge;
} catch (InvalidDnDOperationException exc) {
if (System.getProperty ("netbeans.debug.exceptions") != null) exc.printStackTrace();
// PENDING notify user - cannot start the drag
}
}
public void dragEnter(DragSourceDragEvent dsde) {
}
public void dragOver(DragSourceDragEvent dsde) {
}
public void dropActionChanged(DragSourceDragEvent dsde) {
}
public void dragExit(DragSourceEvent dse) {
}
public void dragDropEnd(DragSourceDropEvent dsde) {
}
/** Utility accessor */
DragGestureEvent getDragGestureEvent () {
return dge;
}
} // end of IndexedDragSource
/** Implementation of drop functionality in
* reorder dialog. */
private static final class IndexedDropTarget
implements DropTargetListener {
/** Asociated JList component for dropping */
JList comp;
/** Cell renderer which renders the list items */
IndexedListCellRenderer cellRenderer;
/** Indexed dialog */
IndexedCustomizer dialog;
/** Drag source support instance */
IndexedDragSource ids;
/** last index dragged over */
int lastIndex = -1;
/** Creates the instance, makes given component active for
* drop operation. */
IndexedDropTarget (IndexedCustomizer dialog, IndexedDragSource ids) {
this.dialog = dialog;
this.comp = dialog.control;
this.cellRenderer =
(IndexedListCellRenderer)this.comp.getCellRenderer();
this.ids = ids;
DropTarget dt =
new DropTarget(comp,
DnDConstants.ACTION_MOVE, this, true);
}
/** User is starting to drag over us */
public void dragEnter(DropTargetDragEvent dtde) {
if (!checkConditions(dtde))
dtde.rejectDrag();
else {
lastIndex = comp.locationToIndex(dtde.getLocation());
cellRenderer.draggingEnter(lastIndex,
ids.getDragGestureEvent().getDragOrigin(),
dtde.getLocation());
comp.repaint(comp.getCellBounds(lastIndex, lastIndex));
}
}
/** User drag over us */
public void dragOver(DropTargetDragEvent dtde) {
if (!checkConditions(dtde)) {
dtde.rejectDrag();
if (lastIndex >= 0) {
cellRenderer.draggingExit();
comp.repaint(comp.getCellBounds(lastIndex, lastIndex));
lastIndex = -1;
}
} else {
dtde.acceptDrag(DnDConstants.ACTION_MOVE);
int index = comp.locationToIndex(dtde.getLocation());
if (lastIndex == index)
cellRenderer.draggingOver(index,
ids.getDragGestureEvent().getDragOrigin(),
dtde.getLocation());
else {
if (lastIndex < 0)
lastIndex = index;
cellRenderer.draggingExit();
cellRenderer.draggingEnter(index,
ids.getDragGestureEvent().getDragOrigin(),
dtde.getLocation());
comp.repaint(comp.getCellBounds(lastIndex, index));
lastIndex = index;
}
}
}
public void dropActionChanged(DropTargetDragEvent dtde) {
}
/** User exits the dragging */
public void dragExit(DropTargetEvent dte) {
if (lastIndex >= 0) {
cellRenderer.draggingExit();
comp.repaint(comp.getCellBounds(lastIndex, lastIndex));
}
}
/** Takes given index transferable and reorders
* the items as appropriate (and if possible) */
public void drop(DropTargetDropEvent dtde) {
// reject all but local moves
if ((DnDConstants.ACTION_MOVE != dtde.getDropAction()) ||
!dtde.isLocalTransfer())
dtde.rejectDrop();
int target = comp.locationToIndex(dtde.getLocation());
if (target < 0) {
dtde.rejectDrop();
return;
}
Transferable t = dtde.getTransferable();
// System.out.println("Dropping..."); // NOI18N
dtde.acceptDrop(DnDConstants.ACTION_MOVE);
try {
int source = Integer.parseInt(
(String)t.getTransferData(ids.myFlavor));
if (source != target) {
performReorder(source, target);
dtde.dropComplete(true);
} else
dtde.dropComplete(false);
} catch (IOException exc) {
dtde.dropComplete(false);
} catch (UnsupportedFlavorException exc) {
dtde.dropComplete(false);
} catch (NumberFormatException exc) {
dtde.dropComplete(false);
}
}
/** Actually performs the reordering which results from
* succesfull drag-drop operation.
* @param source
*/
void performReorder (int source, int target) {
int[] myPerm = new int[comp.getModel().getSize()];
// positions will change only between source and target
// indexes, the rest remains the same
for (int i = 0; i < Math.min(source, target); i++)
myPerm[i] = i;
for (int i = Math.max(source, target) + 1;
i < myPerm.length; i++)
myPerm[i] = i;
// reorder the rest
myPerm[source] = target;
if (source > target) {
// dragging was up the list
for (int i = target; i < source; i++)
myPerm[i] = i + 1;
} else {
// dragging was down the list
for (int i = source + 1; i < target + 1; i++)
myPerm[i] = i - 1;
}
// and finally perform the reordering
dialog.performReorder(myPerm, target);
}
/** @return True if conditions to continue with DnD
* operation were satisfied */
boolean checkConditions (DropTargetDragEvent dtde) {
int index = comp.locationToIndex(dtde.getLocation());
return DnDConstants.ACTION_MOVE == dtde.getDropAction() &&
index >= 0;
}
} // end of IndexedDropTarget
/** This class takes responsibility of presenting the
* asociated index as transferable object.
*/
private static final class IndexTransferable
extends ExTransferable.Single {
/** Index to transfer */
int index;
/** Creates transferable of given index */
IndexTransferable (DataFlavor flavor, int index) {
super(flavor);
this.index = index;
}
/* Returns string representation of index */
protected Object getData ()
throws IOException, UnsupportedFlavorException {
return String.valueOf(index);
}
} // end of IndexTransferable
/** Implements drag and drop visual feedback
* support for node list cell rendeder.
*/
private static final class IndexedListCellRenderer
extends NodeRenderer {
/** Index of currently drag under cell in parent list */
int dragIndex;
/** True if move was up the list */
boolean up;
static final long serialVersionUID =-5526451942677242944L;
protected static Border hasFocusBorder;
static {
hasFocusBorder = new LineBorder(UIManager.getColor("List.focusCellHighlight")); // NOI18N
}
/** Creates new renderer */
IndexedListCellRenderer () {
super();
dragIndex = -1;
}
/** DnD operation enters, update visual
* presentation to the drag under state */
public void draggingEnter (int index, Point startingLoc, Point currentLoc) {
// System.out.println("Entering index: " + index); // NOI18N
this.dragIndex = index;
up = startingLoc.y > currentLoc.y;
}
/** DnD operation dragging over. */
public void draggingOver (int index, Point startingLoc, Point currentLoc) {
}
/** DnD operation exits, reset visual state
* back to the normal */
public void draggingExit () {
dragIndex = -1;
}
public Component getListCellRendererComponent (
JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
JComponent result = (JComponent)super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
if (index == dragIndex) {
// System.out.println("Drawing...."); // NOI18N
result.setBorder(hasFocusBorder);
}
return result;
}
} // end of IndexedListCellRenderer
/** Implements autoscrolling support for JList.
* However, JList must be contained in some JViewport.
*/
private static class AutoscrollJList extends JList
implements Autoscroll {
/** Autoscroll insets */
Insets scrollInsets;
/** Insets for the autoscroll method to decide
* whether really perform or not */
Insets realInsets;
/** Viewport we are in */
JViewport viewport;
static final long serialVersionUID =5495776972406885734L;
/** notify the Component to autoscroll */
public void autoscroll (Point cursorLoc) {
JViewport viewport = getViewport();
Point viewPos = viewport.getViewPosition();
int viewHeight = viewport.getExtentSize().height;
if ((cursorLoc.y - viewPos.y) <= realInsets.top)
// scroll up
viewport.setViewPosition(new Point(viewPos.x,
Math.max(viewPos.y - realInsets.top, 0)));
else if ((viewPos.y + viewHeight - cursorLoc.y) <= realInsets.bottom)
// scroll down
viewport.setViewPosition(new Point(viewPos.x,
Math.min(viewPos.y + realInsets.bottom,
this.getHeight() - viewHeight)));
}
/** @return the Insets describing the autoscrolling
* region or border relative to the geometry of the
* implementing Component.
*/
public Insets getAutoscrollInsets () {
if (scrollInsets == null) {
int height = this.getHeight();
scrollInsets = new Insets(height, 0, height, 0);
// compute also autoscroll insets for viewport
Rectangle rect = getViewport().getViewRect();
realInsets = new Insets(15, 0, 15, 0);
}
return scrollInsets;
}
/** Asociates given viewport with this list.
* (Viewport is usually parent containing this component) */
JViewport getViewport () {
if (viewport == null) {
Component comp = this;
while (!(comp instanceof JViewport) && (comp != null)) {
comp = comp.getParent();
}
viewport = (JViewport)comp;
}
return viewport;
}
} // end of AutoscrollJViewport
}
/*
* Log
* 21 Gandalf 1.20 1/15/00 David Simonek DnD disabled completely
* 20 Gandalf 1.19 1/13/00 Jesse Glick NOI18N
* 19 Gandalf 1.18 1/12/00 Jesse Glick NOI18N
* 18 Gandalf 1.17 11/5/99 Jaroslav Tulach WeakListener has now
* registration methods.
* 17 Gandalf 1.16 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 16 Gandalf 1.15 10/5/99 David Simonek DnD disabled for unixes
* 15 Gandalf 1.14 8/27/99 Jaroslav Tulach
* 14 Gandalf 1.13 8/9/99 Ian Formanek Generated Serial Version
* UID
* 13 Gandalf 1.12 7/25/99 Ian Formanek Exceptions printed to
* console only on "netbeans.debug.exceptions" flag
* 12 Gandalf 1.11 6/10/99 Jaroslav Tulach Commented println
* 11 Gandalf 1.10 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 10 Gandalf 1.9 4/29/99 David Simonek now compiled ok again
* 9 Gandalf 1.8 4/21/99 David Simonek drag and drop support
* added
* 8 Gandalf 1.7 4/9/99 Ian Formanek Removed debug printlns
* 7 Gandalf 1.6 4/2/99 David Simonek listening on Index
* changes added
* 6 Gandalf 1.5 3/27/99 David Simonek
* 5 Gandalf 1.4 3/17/99 Jesse Glick [JavaDoc]
* 4 Gandalf 1.3 3/9/99 Jaroslav Tulach Does not need button bar
* button
* 3 Gandalf 1.2 3/4/99 Jaroslav Tulach
* 2 Gandalf 1.1 1/6/99 Ian Formanek Reflecting changes in
* location of package "awt"
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
*/